package ks.client.game;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;

import ks.client.interfaces.IGameInterface;

/**
 * The Game Manager provides the core logic to host the actual games being
 * considered by KombatGames, including Solitaire, WordSteal and possibly 
 * Sudoku.
 * <p>
 * This is the interface that you will all have to code against.
 * <p>
 * This logic will increasingly become filled in as we progress over the
 * next few weeks.
 * 
 * @author George Heineman
 */
public class GameManager {
	
	/** Singleton instance. */
	static GameManager inst;
	
	/** Lock down the constructor. */
	GameManager() {}
	
	/** Callback interface to project code. */
	IGameInterface callback;
	
	/** Active game window (if one exists). */
	GameFrame frame = null;
	
	/** Return (or create) the singleton instance. */
	public static GameManager instance() {
		if (inst == null) {
			inst = new GameManager();
		}
		
		return inst;
	}
	
	/**
	 * Expose the acceptable game types.
	 * 
	 * @return
	 */
	public static Iterator<String> validGameTypes() {
		return Factory.validGameTypes();
	}
	
	/**
	 * Return default pre-set options for the given game type.
	 * <p>
	 * Provides a useful starting point for the client.
	 * 
	 * @param gameType
	 * @return a preconfigured {@link Properties} object containing suitable defaults. If gameType is invalid
	 * then <code>null</code> is returned.
	 */
	public static Properties defaultOptions(String gameType) {
		Properties opts = new Properties();
		if (Factory.isSolitaire(gameType)) {
			opts.setProperty("time", "300");
			opts.setProperty("undo", Boolean.TRUE.toString());
			opts.setProperty("newHand", Boolean.TRUE.toString());
			return opts;
		} else if (Factory.isWordSteal(gameType)) {
			opts.setProperty("noS", Boolean.FALSE.toString());
			opts.setProperty("pink", Boolean.TRUE.toString());
			opts.setProperty("turnTime", "45");
			opts.setProperty("pointsToWin", "30");
			return opts;
		} else if (Factory.isSudoku(gameType)) {
			opts.setProperty("difficulty", "1");
			return opts;
		}

		// must have been invalid
		return null;
	}
	
	/**
	 * Expose the gameOptions associated with the given game type.
	 * <p>
	 * If an invalid gametype is passed in, then <code>null</code> is passed back. 
	 * 
	 * @return Collection of string names representing options for that game type or <code>null</code> if
	 * game type is invalid.
	 */
	public static Collection<String> validOptions(String gameType) {
		ArrayList<String> options = new ArrayList<String>();
		
		if (Factory.isSolitaire(gameType)) {
			options.add("time");
			options.add("undo");
			options.add("newHand");
			return options;
		} else if (Factory.isWordSteal(gameType)) {
			options.add("noS");
			options.add("pink");
			options.add("turnTime");
			options.add("pointsToWin");
			return options;
		} else if (Factory.isSudoku(gameType)) {
			options.add("difficulty");
			return options;
		}
		
		// invalid
		return null;
	}
	
	/**
	 * Create a game window for the game active on the given table, with the given
	 * options and players.
	 * <p>
	 * A callback object is provided for outgoing calls by the GameManager.
	 * 
	 * @param tableID     table id on which game is to be played
	 * @param me          user that is playing the game on this client 
	 * @param options     Name/Value properties of the options
	 * @param gameOptions Name/Value properties of the game-specific options
	 * @param playerOrder ArrayList describing order of players on table (IDs in this array)
	 * @param playerIDs   Properties where key=ID, name=Real Name
	 * @param callback    call back object whenever we need to communicate back to client.
	 * @return
	 */
	public boolean createGameWindow (int tableID, String me,
			Properties options, Properties gameOptions, 
			ArrayList<String> playerOrder, Properties playerIDs, IGameInterface callback) {
		this.callback = callback;
		
		// figure out what kind of game we are dealing with.
		GameInformation info = Factory.create(options, gameOptions);
		info.setCurrentUser(me);
		info.setPlayers(playerOrder, playerIDs);
		info.setGameInterface(callback);
		
		// grab old history contents
		String oldChat = "";
		if (frame != null) {
			oldChat = frame.getTableTextContents();
			exitGameWindow();
		}
		
		// prepare frame and have it know the user, table and callback.
		frame = new GameFrame(info);
		frame.setTableNumber(tableID);
		frame.setGameInterface(callback);
		
		// throw in the old table chat to give the 'veneer' we are reusing things.
		frame.setTableTextContents(oldChat);
		
		// all ready to go!
		if (!info.start(frame)) {
			System.err.println("Unable to launch game for some reason.");
			return false;
		}
		
		return true;
	}
	
	/** Standard error reporting if no game in progress. */
	private void error(String action) {
		System.err.println("There is no game in progress. Can't perform action: " + action);
	}
	
	/**
	 * When it is the turn of the player on whose client this game is running,
	 * your code must call this method to activate the game controls.
	 * <p>
	 * Note for solitaire games, this will be called on all client computers while
	 * for turn-based games, only one client will perform this at a time. And it will
	 * only be the current player's client that does it.
	 * 
	 * @param playerID
	 * @return <code>true</code> on success; <code>false</code> if problem.
	 */
	public boolean activateTurn (String playerID) {
		if (frame == null) {
			error("activateTurn");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.activateTurn(playerID);
	}
	
	/**
	 * Whenever an update occurs for a player, the client receives the message and then
	 * invokes this method to pass those values along to the game implementation.
	 * <p>
	 * 
	 * @param updatedScores   represents Properties where key=playerID and value=score
	 * @param updatedGame     represents Properties where key=playerID and value=game-specific information
	 *
	 * @return <code>true</code> on success; <code>false</code> otherwise
	 */
	public boolean updateScores (Properties updatedScores, Properties updatedGame) {
		if (frame == null) {
			error("updateScores");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.updateScores(updatedScores, updatedGame);
	}
	
	/**
	 * Whenever a turn for a player occurs, this method must be called to update
	 * the game state.
	 * <p>
	 * This does not advance the turn or anything like that. It simply executes
	 * the described move locally on the game state.
	 * 
	 * @param playerID    player making move
	 * @param moveString  opaque string (from server point of view) that encodes move
	 * @return <code>true</code> on success; <code>false</code> otherwise
	 */
	public boolean makeTurn (String playerID, String moveString) {
		if (frame == null) {
			error("makeTurn");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.makeTurn(playerID, moveString);
	}
	
	/**
	 * Whenever a player decides to skip a turn, this method must be called
	 * to update the game state.
	 * <p>
	 * Note that because this is being invoked in response to a player's move,
	 * the info.skipTurn method MUST NOT generate a message, otherwise infinite
	 * cycle will result.
	 * 
	 * @param playerID    player making move
	 * @return <code>true</code> on success; <code>false</code> otherwise
	 */
	public boolean skipTurn (String playerID) {
		if (frame == null) {
			error("skipTurn");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.skipTurn(playerID);
	}
	
	
	/**
	 * Whenever a player exits a game (on his own volition) all other clients
	 * on that same table must be notified by calling this method.
	 * <p>
	 * Note that this is driven by the observation that another player (other than
	 * this client) has left the table. This is not used when the player himself
	 * chooses to leave a table. For that logic, check out {@link IGameInterface}.
	 * 
	 * @param playerID
 	 * @return <code>true</code> on success; <code>false</code> otherwise
	 */
	public boolean requestLeave (String playerID) {
		if (frame == null) {
			error("requestLeave");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.requestLeave(playerID);

	}
	
	/** Determine whether a game is active. */
	public boolean isGameActive() {
		return frame != null;
	}
	
	/**
	 * When the main client exits, it must send a fare-thee-well message 
	 * to eliminate the game window, if it exists.
	 */
	public void exitGameWindow() {
		if (frame != null) { 
			frame.closeDown();
		}
		frame = null;
	}
	
	/**
	 * Request to post text to the tableChat window in the GameWindow from
	 * the given player.
	 * 
	 * @param fromPlayerID
	 * @param text
	 */
	public void showTableText (String fromPlayerID, String text) {
		if (frame == null) {
			error("activateTurn");
			return;
		}
		
		frame.showTableText(fromPlayerID, text);
	}

	/**
	 * Determine if the game is in its pristine newly started state.
	 * 
	 * @return <code>true</code> if no move has yet to be made on the board; <code>false</code> otherwise.
	 */
	public boolean isFirstTurn() {
		if (frame == null) {
			error("isFirstTurn");
			return false;
		}
		
		GameInformation info = frame.getGameInformation();
		return info.isFirstTurn();
	}

	/**
	 * Glorified hack.
	 * 
	 * Since protocol has no way to record a 'skip' we need to alert all other non-moderator
	 * players when the first teun is a skip. Use this hack for this purpose.
	 */
	public void hackFirstSkipReceived() {
		if (frame == null) {
			error("hackFirstSkipReceived");
			return;
		}
		
		GameInformation info = frame.getGameInformation();
		info.hackFirstSkipReceived();
		
	}
}
